package nayasis.common.device.input;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.java.games.input.Component;
import net.java.games.input.Component.Identifier;
import net.java.games.input.Controller;

/**
 * 게임패드 이벤트
 */
public class GamePadEvent {

	/**
	 * 게임패드 방향키 값
	 */
	public static enum POV { OFF, UP, UP_RIGHT, RIGHT, DOWN_RIGHT, DOWN, DOWN_LEFT, LEFT, UP_LEFT, ERR }
	
	private int        index      = 0;
	private Controller controller = null;
	
	private Map<String, Float> povInput    = new HashMap<>(); // {id, val}
	private Map<String, Float> axisInput   = new HashMap<>(); // {id, val}
	private Map<String, Float> buttonInput = new HashMap<>(); // {id, val}
	
	/**
	 * 기본 생성자
	 * 
	 * @param index      게임패드 연결순서
	 * @param controller 게임패드 컨트롤러
	 */
	public GamePadEvent( int index, Controller controller ) {
		
		this.index      = index;
		this.controller = controller;
		
	}

	/**
	 * 게임패드의 입력상태를 조사한다.
	 * 
	 * @return 게임패드 입력상태 조사 성공여부 (게임패드 연결이 끊어졌을 경우 false 반환)
	 */
	public boolean poll() {
		
		povInput.clear();
		axisInput.clear();
		buttonInput.clear();
		
		if( ! controller.poll() ) return false;
			
		for( Component c : controller.getComponents() ) {
			
			Identifier id  = c.getIdentifier();
			float      val = c.getPollData();

			if( isPov(id) ) {

				if( val == 0.0 ) continue;

				povInput.put( id.getName(), val );
				
			} else if( isAxis(id) ) {
				
				if( Math.floor(Math.abs(val) * 100) == 0.0 ) continue;

				// convert value of Y-Axis
				if( id == Identifier.Axis.Y  || id == Identifier.Axis.RZ ) {
					val = - val;
				}
				
				axisInput.put( id.getName(), val );

			} else {
				
				if( val == 0.0 ) continue;
				
				buttonInput.put( id.getName(), val );
				
			}
			
		}
		
		return true;
		
	}
	
	/**
	 * 게임패드 이름을 구한다.
	 * 
	 * @return
	 */
	public String getName() {
		return controller.getName();
	}

	/**
	 * 몇 번째로 연결되어 있는 device인지를 구한다.
	 * 
	 * @return device 연결순서
	 */
	public int getIndex() {
		return this.index;
	}
	
	/**
	 * 방향키가 입력되었는지 여부를 확인한다.
	 * 
	 * @return 방향키 입력여부
	 */
	public boolean isPovPressed() {
		return povInput.size() > 0;
	}

	/**
	 * 아날로그 패드가 입력되었는지 여부를 확인한다.
	 * 
	 * @return 아날로그 패드 입력여부
	 */
	public boolean isAxisPressed() {
		return axisInput.size() > 0;
	}
	
	/**
	 * 버튼이 입력되었는지 여부를 확인한다.
	 * 
	 * @return 버튼 입력여부
	 */
	public boolean isButtonPressed() {
		return buttonInput.size() > 0 ;
	}

	/**
	 * 게임패드로 어떤 값이 입력되었는지 여부를 확인한다.
	 * 
	 * @return 게임패드 신호 인입여부
	 */
	public boolean isAnyPressed() {
		return isPovPressed() || isAxisPressed() || isButtonPressed();
	}

	/**
	 * 방향키 값을 읽는다.
	 * 
	 * @return 방향키 값
	 */
	public POV getPovValue() {
		
		if( ! isPovPressed() ) return POV.OFF;
		
		float val = povInput.get( "pov" );
		
		if( val == Component.POV.UP         ) return POV.UP;
		if( val == Component.POV.UP_RIGHT   ) return POV.UP_RIGHT;
		if( val == Component.POV.RIGHT      ) return POV.RIGHT;
		if( val == Component.POV.DOWN_RIGHT ) return POV.DOWN_RIGHT;
		if( val == Component.POV.DOWN       ) return POV.DOWN;
		if( val == Component.POV.DOWN_LEFT  ) return POV.DOWN_LEFT;
		if( val == Component.POV.LEFT       ) return POV.LEFT;
		if( val == Component.POV.UP_LEFT    ) return POV.UP_LEFT;

		return POV.ERR;
		
	}

	/**
	 * 왼쪽 아날로그 스틱의 X 좌표를 불러온다.
	 * 
	 * @return X 좌표 ( left : -1, right : 1 )
	 */
	public float getLeftAxisX() {
		
		Float val = axisInput.get( "x" );
		
		return ( val == null ) ? 0 : val;
		
	}

	/**
	 * 왼쪽 아날로그 스틱의 Y 좌표를 불러온다.
	 * 
	 * @return Y 좌표 ( down : -1, up : 1 )
	 */
	public float getLeftAxisY() {
		
		Float val = axisInput.get( "y" );
		
		return ( val == null ) ? 0 : val;
		
	}

	/**
	 * 오른쪽 아날로그 스틱의 X 좌표를 불러온다.
	 * 
	 * @return X 좌표 ( left : -1, right : 1 )
	 */
	public float getRightAxisX() {
		
		Float val = axisInput.get( "z" );
		
		return ( val == null ) ? 0 : val;
		
	}
	
	/**
	 * 오른쪽 아날로그 스틱의 Y 좌표를 불러온다.
	 * 
	 * @return Y 좌표 ( down : -1, up : 1 )
	 */
	public float getRightAxisY() {
		
		Float val = axisInput.get( "rz" );
		
		return ( val == null ) ? 0 : val;
		
	}
	
	/**
	 * id에 해당하는 게임패드 입력값을 구한다.
	 * 
	 * @param id 입력ID
	 * @return 입력값
	 */
	public Float getValue( String id ) {
		
		Float result = povInput.get( id );
		
		if( result == null ) result = axisInput.get( id );
		if( result == null ) result = buttonInput.get( id );
		
		return result;
		
	}
	
	/**
	 * 저장되어있는 게임패드 입력 id 목록을 가져온다.
	 * 
	 * @return 게임패드 입력 id 목록
	 */
	public List<String> keySet() {
		
		List<String> result = new ArrayList<>();
		
		for( String key : povInput.keySet() ) {
			result.add( key );
		}

		for( String key : axisInput.keySet() ) {
			result.add( key );
		}
		
		for( String key : buttonInput.keySet() ) {
			result.add( key );
		}
		
		return result;
		
	}
	
	/**
	 * 식별자가 방향키인지 여부를 확인한다.
	 * 
	 * @param id 식별자
	 * @return 방향키 여부
	 */
	private boolean isPov( Identifier id ) {
		
		if( id == null ) return false;
		
		return Identifier.Axis.POV == id;
		
	}

	/**
	 * 식별자가 아날로그 패드인지 여부를 확인한다. 
	 * 
	 * @param id 식별자
	 * @return 아날로그 패드 여부
	 */
	private boolean isAxis( Identifier id ) {
		
		if( id == null ) return false;
		
		// left stick
		if( id == Identifier.Axis.X  ) return true; // left : -1, right : 1
		if( id == Identifier.Axis.Y  ) return true; // up   : -1, down  : 1

		// right stick
		if( id == Identifier.Axis.Z  ) return true; // left : -1, right : 1
		if( id == Identifier.Axis.RZ ) return true; // up   : -1, down  : 1

		return false;

	}

	public String toString() {
		
		StringBuffer sb = new StringBuffer();
		
		sb.append( String.format("%dth GamePad ", index) );
		
		for( String key : keySet() ) {
			
			sb.append( String.format(" | [%s] %.3f", key, getValue(key)) );
			
		}
		
		return sb.toString();
		
	}
	
}
